﻿using UnityEngine;
using System.Collections;
using UnityEditor;
using System.Collections.Generic;
using System;
using System.Reflection;
using System.Linq;
using System.Text.RegularExpressions;
using System.Text;

[CustomEditor(typeof(CameraController))]
public class CameraInspectorEditor : Editor
{
	private bool[] m_cameraDefinitionFoldoutStates;
	private Dictionary<string, string> m_fieldMapping;

	private bool m_isDirty;

	public CameraInspectorEditor()
	{
		m_fieldMapping = new Dictionary<string, string>();

		m_fieldMapping.Add("Single", "Float");
	}
	
	private string AddSpaces(string value)
	{
		StringBuilder builder = new StringBuilder();
		foreach (char c in value) 
		{
			if (Char.IsUpper(c) && builder.Length > 0) builder.Append(' ');
			builder.Append(c);
		}

		return builder.ToString();
	}
	
	private void CreateDefinitionInspectorPanels(CameraController targetController)
	{
		List<ICameraDefinition> cameraDefinitions = targetController.CameraDefinitions;

		// Initialize the array who will hold our inspector panel states.
		if (m_cameraDefinitionFoldoutStates == null || 
		    m_cameraDefinitionFoldoutStates.Length != cameraDefinitions.Count)
		{
			m_cameraDefinitionFoldoutStates = new bool[cameraDefinitions.Count];
		}

		// Loop through all of the camera definitions and create the inspector.
		for (int i = 0; i < cameraDefinitions.Count; i++) 
		{
			// Creates a buffer.
			bool foldOutState = m_cameraDefinitionFoldoutStates[i];

			// Creates the foldout with the specified title.
			foldOutState = EditorGUILayout.Foldout(foldOutState, cameraDefinitions[i].DefinitionInspectorName);

			// We only create the panel if it is expanded.
			if (foldOutState)
				ParseObject(cameraDefinitions[i], cameraDefinitions[i].GetType());

			// Set buffer back to the list of values.
			m_cameraDefinitionFoldoutStates[i] = foldOutState;
		}
	}
	
	public override void OnInspectorGUI()
	{
		CameraController targetController = target as CameraController;
		
		ParseObject(target, typeof(CameraController));
		CreateDefinitionInspectorPanels(targetController);
		
		if (m_isDirty)
		{
			EditorUtility.SetDirty(targetController);
			m_isDirty = false;
		}
	}

	private void ParseObject(object instance, Type type)
	{
		// Get all the fields for the given type that are instance type and public.
		IOrderedEnumerable<FieldInfo> fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public)
			.Where(field => !field.FieldType.GetInterfaces()
			       .Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
			// Order the list by name.
			.OrderBy(field => field.Name);

		IEnumerable<MethodInfo> methods = typeof(EditorGUILayout).GetMethods(BindingFlags.Static | BindingFlags.Public)
			.Where(x => x.Name.Contains("Field"));

		if (methods != null && methods.Count() > 0)
		{
			// Loop through the fields.
			foreach (var field in fields) 
			{
				// Gets the type name.
				// Maps type names from their generic name to their C# counterpart.
				// ie: Single => Float
				string typeName = m_fieldMapping.Keys.Contains(field.FieldType.Name) ?
					m_fieldMapping[field.FieldType.Name] :
					field.FieldType.Name;

				// Get the first method that has a string parameter.
				MethodInfo fieldLayoutMethod = methods
					.Where(method => method.GetParameters()
					       .Any(x => x.ParameterType == typeof(string)))
					.FirstOrDefault(method => method.Name.IndexOf(typeName, StringComparison.OrdinalIgnoreCase) != -1);


				
				if (fieldLayoutMethod != null)
				{
					object originalValue = field.GetValue(instance);

					// Call the method relating to the property.
					object newValue = fieldLayoutMethod.Invoke(null, new object[]
					{
						AddSpaces(field.Name),
						originalValue,
						null
					});

					if (!originalValue.Equals(newValue))
					{
						field.SetValue(instance, newValue);
						m_isDirty = true;
					}
				}
				else
				{
					object originalValue = field.GetValue(instance);

					// Fall back to the object field.
					object newValue = EditorGUILayout.ObjectField(field.Name, field.GetValue(instance) as UnityEngine.Object,
						field.FieldType, true);

					if (!originalValue.Equals(newValue))
					{
						field.SetValue(instance, newValue);
						m_isDirty = true;
					}
				}
			}
		}
	}
}
